from __future__ import division

import random

from concepts import *

from .building import Building
from .logistics import Logistics
from .person import Person


class City(Object):
    wage =_ (5)
    tax  =_ (0.15)
    
    name       = Attribute()
    map        = Attribute()
    calendar   = Attribute()
    population = Attribute(initial =     0, writable = True)
    workers    = Attribute(initial =     0, writable = True)
    employed   = Attribute(initial =     0, writable = True)
    jobs       = Attribute(initial =     0, writable = True)
    housing    = Attribute(initial =     0, writable = True)
    money      = Attribute(initial = 25000, writable = True)
    index      = Attribute(initial = {}, convert = dict, transform = dict)
    triggers   = Attribute(initial = [], convert = list, transform = list)
    logistics  = Attribute()
    
    virtual = SetAttribute()
    
    def setup(self):
        self.logistics = Logistics()
        self.map.city = self
        
        for _ in range(self.calendar.ticks_per_day):
            self._triggers.append([])
    
    @property
    def unemployed(self):
        return int(max(self.total_workers - self.jobs, 0))
    
    @property
    def openings(self):
        return int(max(self.jobs - self.total_workers, 0))
    
    @property
    def total_workers(self):
        return self.workers + self.employed
    
    @property
    def needs_workers(self):
        return self.openings > 0
    
    @property
    def unemployment(self):
        if self.total_workers == 0:
            return 0
        else:
            return self.unemployed / self.total_workers
    
    @property
    def free_housing(self):
        return self.housing - self.population
    
    def can_place(self, type, position):
        for dx in range(type.size):
            for dy in range(type.size):
                if not self.map.at(position.in_direction(dx, dy)).can_build(type):
                    return False
        
        return True
    
    def can_place_virtual(self, type, position):
        if not self.can_place(type, position):
            return False
        
        for dx in range(type.size):
            for dy in range(type.size):
                position = position.in_direction(dx, dy)
                
                if any(position in virtual.area for virtual in self.virtual):
                    return False
        
        return True
    
    @check(can_place)
    def place(self, type, position):
        building = type(self, position)
        current  = self.map.at(position).building
        
        if current is not None:
            self.remove(current)
        
        for super_type in type.mro():
            self.add_index(super_type, building)
        
        self.map.add(building)
        self._triggers[id(building) % self.calendar.ticks_per_day].append(building)
        
        return building
    
    @check(can_place_virtual)
    def place_virtual(self, type, position):
        building = type(self, position, virtual = True)
        
        self._virtual.add(building)
        
        return building
    
    def clear_virtual(self):
        self._virtual.clear()
    
    @check(can_place)
    def build(self, type, position):
        self.money -= type.cost
        
        return self.place(type, position)

    def realize(self, building):
        if not building.virtual:
            return
        
        self.money -= building.type.cost
        
        self._virtual.discard(building)
        
        current = self.map.at(building.position).building
        
        if current is not None:
            self.remove(current)
        
        for super_type in building.type.mro():
            self.add_index(super_type, building)
        
        self.map.add(building)
        self._triggers[id(building) % self.calendar.ticks_per_day].append(building)
    
    def spawn(self, type, position, **attributes):
        person = type(self, position, **attributes)
        
        for super_type in type.mro():
            self.add_index(super_type, person)
        
        return person
    
    @property
    def buildings(self):
        return self.every(Building)
    
    @property
    def people(self):
        return self.every(Person)
    
    def add_index(self, key, value):
        if not hasattr(self, '_index'):
            self._index = {}
        
        if key not in self._index:
            self._index[key] = list()
        
        self._index[key].append(value)
    
    def remove_index(self, key, value):
        if not hasattr(self, '_index'):
            self._index = {}
        
        if key not in self._index:
            return
        
        self._index[key].remove(value)
    
    def index(self, key):
        if not hasattr(self, '_index'):
            self._index = {}
        
        if key not in self._index:
            return []
        else:
            return self._index[key][:]
    
    # FIXME: Buildings aren't properly deallocated
    def remove(self, stuff, update_object = True):
        if update_object:
            stuff.destroy(update_city = False)
        
        if isinstance(stuff, Building):
            for position in stuff.area:
                self.map.at(position).building = None
            
            self._triggers[id(stuff) % self.calendar.ticks_per_day].remove(stuff)
        
        for super_type in stuff.__class__.mro():
            self.remove_index(super_type, stuff)
        
        stuff.position = None
    
    def count(self, cls, predicate = None):
        return len(self.every(cls, predicate))
    
    def every(self, cls, predicate = None):
        if predicate is None:
            return self.index(cls)
        else:
            return filter(predicate, self.index(cls))
    
    def random(self, cls, predicate = None):
        stuff = self.every(cls, predicate)
        
        if stuff:
            return random.choice(stuff)
        else:
            return None
    
    def on_tick(self):
        self.calendar.next_tick()
        
        # Cache everything in local vars to avoid slowing down the tight loops below.
        day      = self.calendar.day
        month    = self.calendar.month
        year     = self.calendar.year
        midnight = self.calendar.is_start_of_day
        payday   = self.calendar.is_start_of_month
        
        if midnight:
            self.map.day(day, month, year)
        
        for building in self.buildings:
            if payday:
                building.payday()
            
            building.on_tick()
        
        for building in self.triggers[self.calendar.tick_of_day]:
            building.on_day(day, month, year)
        
        for person in self.people:
            if midnight:
                person.on_day(day, month, year)
            
            person.on_tick()
    
    def add_money(self, num):
        self.money += num
